import { ImagePSD, TreePSD } from 'psd';

import { Layers, Node, Sprite, UITransform, Vec2, Vec3 } from './cc';
import { AssertServiceBase, ConverterFactoryBase, FileFactoryBase, IConverter } from '../../contract';
import { enum_ } from '../../model';

export abstract class ConverterBase implements IConverter {
    public static getCcPropFunc = (name: string) => {
        return require('cc')[name];
    };

    private m_X?: number;
    protected get x() {
        if (!this.m_X) {
            this.m_X = this.treePSD.get<number>(enum_.Psd.left);
            const parentTreePSD = this.treePSD.get<TreePSD>(enum_.Psd.parent);
            if (!parentTreePSD.isRoot())
                this.m_X -= parentTreePSD.get<number>(enum_.Psd.left);
        }
        return this.m_X;
    }

    private m_Y?: number;
    protected get y() {
        if (!this.m_Y) {
            this.m_Y = -this.treePSD.get<number>(enum_.Psd.top);
            const parentTreePSD = this.treePSD.get<TreePSD>(enum_.Psd.parent);
            if (!parentTreePSD.isRoot())
                this.m_Y += parentTreePSD.get<number>(enum_.Psd.top);
        }
        return this.m_Y;
    }

    public constructor(
        protected assetService: AssertServiceBase,
        protected factory: ConverterFactoryBase,
        protected fileFactory: FileFactoryBase,
        protected treePSD: TreePSD,
    ) { }

    protected async addChildren(node: any) {
        const children = this.treePSD.children();
        for (let i = children.length - 1; i >= 0; i--) {
            if (children[i].get<string>(enum_.Psd.name).includes('.Sprite')) {
                await this.appendSprite(children[i], node);
            } else {
                const childNode = await this.factory.build(children[i]).convert();
                if (!childNode)
                    continue;

                childNode.parent = node;
            }
        }
    }

    protected appendComponent<T>(typer: string | (new () => T), node: any) {
        if (typeof typer == 'string')
            return node.addComponent(typer) as T;

        const componentCtor = ConverterBase.getCcPropFunc(typer.name);
        return node.addComponent(componentCtor) as T;
    }

    protected async appendSprite(treePSD: TreePSD, node: any) {
        let name = treePSD.get<string>(enum_.Psd.name).split('.').shift();
        const texturePath = `texture/${name}.png`;
        await treePSD.get<ImagePSD>(enum_.Psd.image).saveAsPng(
            this.fileFactory.buildFile(texturePath).path,
        );
        const ctor = ConverterBase.getCcPropFunc(Sprite.name);
        const sprite = node.addComponent(ctor);
        sprite.sizeMode = ctor.SizeMode.CUSTOM;
        sprite.spriteFrame = await this.assetService.load(texturePath, 'spriteFrame');
        return sprite;
    }

    protected createNode(opt?: Partial<{
        x: number;
        y: number;
        name: string;
    }>) {
        opt ??= {};

        const nodeCtor = ConverterBase.getCcPropFunc(Node.name);
        const node = new nodeCtor(
            opt.name ?? this.treePSD.get<string>(enum_.Psd.name)
        );
        const layersCtor = ConverterBase.getCcPropFunc(Layers.name);
        node.layer = layersCtor.Enum.UI_2D;
        node.position = this.createVec3(opt.x ?? this.x, opt.y ?? this.y);

        const transform = this.appendComponent(UITransform, node);
        transform.height = this.treePSD.get<number>(enum_.Psd.height);
        transform.width = this.treePSD.get<number>(enum_.Psd.width);
        const vec2Ctor = ConverterBase.getCcPropFunc(Vec2.name);
        transform.anchorPoint = new vec2Ctor(0, 1);
        return node;
    }

    protected createVec3(x: number, y: number) {
        const vec3Ctor = ConverterBase.getCcPropFunc(Vec3.name);
        return new vec3Ctor(x, y, 0);
    }
    public abstract convert(): Promise<any>;
}